Charlie Calvert's C++ Builder Unleashed
- 17 -
Printing: QuickReport
and Related Technologies
Overview
In this chapter you will get a look at seven ways to print data:
- Printing data from databases via QuickReport
- Printing QuickReport data in multiple columns and rows
- Printing QuickReport data by group
- Printing QuickReport data in one-to-many relationships
- Using the TPrinter object to print a graphic image of a form exactly
as the form appears to the user at runtime
- Printing via the GDI, so that you can duplicate, fonts, pictures, and shapes
drawn through the GDI
- Using ReportSmith
I am going to explain QuickReport first, since it is by far the most important
technique. However, you should be sure to glance down at the other techniques, because
they are valuable and can be absorbed fairly quickly.
Printing has traditionally been a complicated subject in Windows. In DOS, it was
easier, but still very time-consuming. In BCB, all forms of printing are easy to
master, and QuickReport is a subject most programmers can learn in a few hours and
master in a few days. After you understand the basics of how to use QuickReport,
you should be able to prepare many reports in well under an hour. Even if you were
a perfectionist struggling to put together a complicated report, it is unlikely that
you would spend more than a few hours on a report.
If you are familiar with ReportSmith or Crystal Reports, you might be inclined
to use them first without examining QuickReport. I would, however, spend the time
it takes to get up to speed on QuickReport. You might be surprised to find how powerful
this tool can be. Certainly, QuickReport is almost preternaturally easy to use, especially
when considering how much quicker it makes a traditionally long, complex and frustrating
task.
Having said all this, I have to confess that printing is still a task I don't
enjoy much. I personally don't have much need for printed reports, because they take
up space, become outdated quickly, and are very difficult to track. As a result,
I prefer to keep information in electronic form. However, I recognize the importance
of printing, and will deal with it in this chapter in considerable depth.
One hint that can help you ease the burden of creating reports is to let SQL statements
carry the burden of the work. If you need to create a particular report, write a
SQL statement that will generate it as nearly as possible, and then let the statement
do double duty as a way of preparing an electronic report, and as a way of preparing
a paper-based report. This technique makes the task less onerous to me, and it is
usually the quickest way to complete the chore. In this chapter, I examine this technique
in my analysis of making a grouped report for the Music program.
QuickReport Basics
QuickReport was originally a third-party tool written in Object Pascal that the
VCL team decided to incorporate into the product. This unusual move came about as
a result of the high quality of this product, along with its relatively close conformance
to the VCL programming paradigm. You will, however, notice a few rough edges because
some of the conventions in this product do not exactly match the conventions used
elsewhere in the VCL.
NOTE: The rough edges between the VCL
and QuickReport will probably be smoothed over in later releases. As a result, you
should be prepared for the possibility that your code in this one area might break
in future releases of BCB. However, QuickReport is easy to use, and the code associated
with it is by definition isolated from the rest of the code in your application.
As a result, you should be able to adapt to the change fairly easily.
Because QuickReport is made by a third party, you should look on the Web to see if
you can find updates to this product. For instance, Delphi 3 will ship with a version
of QuickReport that is much more sophisticated than the version included with BCB
1.0.
The basic idea behind QuickReport is to provide a set of components that are as
closely parellel as possible to the tools you use when creating a standard BCB form.
For instance, Table 17.1 gives a list of standard visual controls and visual database
controls with their QuickReport equivalents.
Table 17.1. Standard VCL controls on the left and QuickReport equivalents on the
right.
| VCL controls |
QuickReport controls |
| TLabel |
TQRLabel |
| TDBEdit |
TQRDBText |
| TDBLabel |
TQRDBText |
| TDBMemo |
TQRMemo |
| TShape |
TQRShape |
As you can see, there is no difference between a QuickReport label and a QuickReport
edit control. Needless to say, this makes sense when you consider the fact that you
can't edit a field in a report!
Each of the data-aware controls has a DataSource and DataField
property. The TQRLabel and TQRShape controls do not have a these
properties, because they are not data-aware.
Each report that you generate appears on its own form. The act of creating the
form is a simple matter of laying out TQRMemo and TQRDBText controls
on a series of bands called TQRBand controls.
One visual QuickReport component that does not fit into any predefined category
is called TQRSysData. This component can display the current project title,
page number, date, time, or record count. The particular function adopted by the
component is controlled by its Data property. I will explain how to use
the component in more depth later in the chapter.
Each time you create a new QuickReport form, you should also drop down a TQuickReport
component and at least one TQRBand component. (Don't confuse the TQuickReport
component with the product itself, which is called QuickReport.) The TQuickReport
component is a "magic" component that converts a regular form into a QuickReport
form. All you need to do is drop the component on the form and set its DataSource
property to a TDataSource, just as you would when using a TDBGrid.
The TQRBand component is the surface on which you should place the QuickReport
visual controls.
NOTE: In most reports you want to print
multiple rows, as you would when showing data in a grid. To do this, set the BandType
property of a TQRBand report to rbDetail. If you want to have multiple
columns in your report, set the Columns property of TQuickReport
to the number of columns you want to use. I will explain all this in more depth later
in this chapter.
After you finish designing a form, you can run the program and call the Preview
method of TQuickReport to view the output. The preview form has a print
button on it so you can print the report. If you want, you can skip the preview screen
and directly call the Print method of TQuickReport.
The great thing about QuickReport is that it lets you use existing datasets, including
calculated fields and lookup fields, that are already part of your project. Rather
than concerning yourself with creating custom queries or with setting up sort orders,
you can just plug your QuickReport components directly into your existing datasets.
If you sort the data in some particular way on a form, that is the same sort order
you see when you pop up a QuickReport based on the tables used by that form.
Using TQRBand: Creating Rows and Columns in QuickReport
Most people can figure out how to use QuickReport without help from a book like
this. However, there are a few things that people sometimes can't figure out on their
own:
- How to print rows of data
- How to print columns of data
- How to group data
- How to create a one-to-many report
All four of these subjects will be covered in this chapter. I've already described
how to perform the first two chores earlier, but I will repeat the information here,
just so these key bits of information will be as easy as possible to find.
To create multiple rows of data, set the BandType property of a TQRBand
report to rbDetail. If you want to have multiple columns in your report,
set the Columns property of TQuickReport to the number of columns
you want to use. That's all you have to do.
If you dig into the QuickReport controls a bit, you will find the TQRBand
component has a BandType property that can be set to one of the values in
Table 17.2.
Table 17.2. BandType property values and purposes.
| Value |
Purpose |
| rbTitle |
A title printed once at the start of the report. |
| rbPageHeader |
Appears at the top of each page. |
| rbDetail |
Use this type if you want to repeat many rows of data, or for use with one-to-many
reports. |
| rbSubDetail |
The detail band in a one-to-many report. This connects the DetailGroup band
to the DetailLink component using the QRDetailLink.DetailBand property. |
| rbPageFooter |
Creates a footer at the bottom of the page. |
| rbSummary |
Creates a summary at the end of the report. |
| rbGroupHeader |
Creates group headers for use with QRGroup and QRDetailLink |
| components. |
|
| rbGroupFooter |
Creates footers for use with QRGroup and QRDetailLink components. |
| rbColumnHeader |
Labels each column in a column-based report. |
| rbOverlay |
Floats on top of all other text and graphics printed on the page. |
The best way to learn about these properties is through experience. In particular,
you will find that the BandType property should be set to reflect the types
of components being used in a particular context. For instance, the TQRSysData
component, which is often used to display the date, time, or page count will generally
go on TQRBand components set to rbPageHeader or rbPageFooter.
This is only logical, because page count, date, and time information usually appears
on the header or footer of a report.
The easiest way to learn how to use TQRBands and their related components
is through experience. The next several sections of this chapter give working examples
of using these components.
Working
with the Sample Programs
In the next few pages I describe the reports from all three major database programs
found in this book:
- The reports for the flat file Address2 program illustrate the basic facts about
using QuickReport, including grouping.
- The reports for the relational kdAdds and Music program show how to create master
detail reports, and how to write custom queries that make it easy to create reports.
Many people could probably learn how to use QuickReport by running these programs
and popping up the various preview pages. After they are familiar with the different
styles I use in my preview pages, they can just go back to design mode and study
my QuickReport forms to see how I achieved a particular effect. In other words, I
expect many people will learn by example.
The text that follows is therefore a bit sketchier than what you find elsewhere
in the book. The issue here is simply that this is primarily a mechanical task that
takes only a minimal amount of understanding. As a result, I will try to point you
in the right direction wherever possible, and then step out of the way so you can
experiment on your own computer.
The Address2 Program
I add five reports to the Address2 program from Chapter 13, "Flat-File, Real-World
Databases." The first prints only addresses, the second prints only phone numbers,
the third prints all the data in the report, and the fourth prints reports by grouping
them on the Category field. Later in the chapter, I will also include a
section that describes how to print a report from inside of ReportSmith.
Address2: Printing
Address
This report has the task of printing out addresses. I do not include phone numbers,
just addresses--as if I were making labels. A copy of the finished report as it appears
in preview mode is shown in Figure 17.1. Figure 17.2 shows the same report in design
mode.
To create the report, start by laying down a TQuickReport component.
Connect it to the AddressSource on the program's data module. To do this,
you will need to include DMod1 in your project by pulling down the File
menu and selecting Include Unit Header.
FIGURE
17.1. The Address Report from the Address2
program.
FIGURE
17.2. The Address Report in design mode.
NOTE: I don't think you can make it through
this chapter without at least a minimal understanding of the Address2 program. This
program was discussed in depth in Chapter 13. The source for that program, including
the reports it uses, are found in the Chap13 directory on the CD that accompanies
this book.
If you want to work along with me by creating the forms on your machine as I describe
them in this book, open up the Address2 program, and add a new form to it. There
is no harm in having two forms in the program that perform the same task; that way,
you can easily open up my sample form if you get stuck.
Remember, this material does not cause brain strain. You just need to understand
how these components work, and then you will be able to write your own reports.
Now drop down three TQRBand components. Don't place them one on top of
the other; lay one down, click on the main form, lay the next one down, and so on.
That way, they are positioned one above the other on the main form. The final position
of the controls at runtime is determined by the BandType property of each
TQRBand. If you want to change the order of the controls on the form, drag
them around with the mouse, the same way you move items in the Tab Order list box
from the Edit menu. The Align property for these forms can be left at the
default position of alTop. Remember, it usually doesn't really matter what
order these controls have on your form; what matters is the BandType property.
The BandType for the top QRBand control should be set to rbTitle,
the next one should be set to rbDetail, and the last one should be set to
rbPageFooter. Drop down the Frame property of the detail band and
set its DrawLeft, DrawRight, DrawTop, and DrawBottom
properties to True.
The title band should have a TQRLabel component placed on it. Set its
alignment to taCenter and set AlignToBand to True. Use
the Font property to select a large bold font. Set the Caption
property to the title of the report.
Another way to create a title involves using the TQRSysData component.
Drop one down on the title band and set its alignment and font as you did the TQRLabel.
Instead of changing the Caption property of this component, set its Data
property to qrsReportTitle. Click on the TQuickReport component
and set its ReportTitle property to the name of the report. For instance,
you might set it to Address Report. Now when you run the report, the title
will show up automatically at the top of the first page.
On the detail band, drop down four TQRDBText controls and set their DataSource
property to the AddressSource from the program's data module and their fields
to FirstLast, Address1, Address2, and CityStateZip.
The first and last fields are calculated fields. The code for creating the calculated
fields looks like this:
void __fastcall TDMod::AddressTableCalcFields(TDataSet *DataSet)
{
if ((!AddressTableFName->IsNull) || (!AddressTableLName->IsNull))
AddressTableFirstLast->Value =
AddressTableFName->Value + " " + AddressTableLName->Value;
else if (!AddressTableCompany->IsNull)
AddressTableFirstLast->Value = AddressTableCompany->Value;
else
AddressTableFirstLast->Value = "Blank Record";
AddressTableCityStateZip->Value = AddressTableCity->Value + " " +
AddressTableState->Value + ", " + AddressTableZip->Value;
}
The code for creating the AddressTableFirstLast field was described during
the initial discussion of the Address2 program in Chapter 13. The code for creating
the AddressTableCityStateZip field involves nothing more than concatenating
the three fields, and adding spaces and commas where appropriate. In a professional
program, you might want to add more code to eliminate the possibility that a blank
set of fields would generate a string that consists of nothing but a comma. I will
omit such code here so that you can more easily decipher the code I do include.
To display more than one column in a report, turn to the TQuickReport
component and set its Column property to the value you desire, such as 2
or 3.
The final band in the report cover appears as a footer with the current date and
time on it, as well as the page number. You can use the TQRSysData component
to retrieve this data. Set one component's Data property to qrsDateTime
and the other to qrsPageNumber.
If you want to show a print dialog to the user, you can do so in the BeforePrint
event of the TQuickReport component:
void __fastcall TAddressReport::QuickReport1BeforePrint(bool &PrintReport)
{
if (PrintDialog1->Execute())
{
PrintReport = True;
}
else
{
PrintReport = False;
}
}
This code pops up a print dialog, as shown in Figure 17.3. The TPrintDialog
component is found on the Dialogs page of the Component Palette. The settings selected
by the user in the print dialog are sometimes passed on to the system without intervention
on your part. However, if you need to access the settings, you can do so via the
fields of the component. You can then pass this information on to QuickReport if
necessary.
FIGURE
17.3. Showing a print dialog so the user
can select a printer.
NOTE: There is a button on the TPrintDialog
control that will pop up a setup dialog for the printer. As a result, there is rarely
any reason for you to include a TPrinterSetupDialog in your program. The
act of including a TPrintDialog covers both bases automatically.
To show the report to the user, you can set up a menu item on the main form of
the program with its caption set to the string "Address Report".
This is what the response to a click on this control should look like:
void __fastcall TForm1::PrintAddresses1Click(TObject *Sender)
{
AddressReport->QuickReport1->Preview();
}
This assumes the name of the form on which the report is stored is called AddressReport,
and the unit containing this form has been included in the main form:
#include "QRAddress1.h"
The Preview screen has a button on it that allows the user to print the report.
If you didn't want to show the preview first, you can call PRINT directly:
AddressReport->QuickReport1->Print();
As you can see, creating reports using TQuickReport is trivial in the
extreme. This is the kind of operation you can usually roll out in about 30 minutes,
or less. If you find things getting complicated, you can usually clean up the mess
by writing a query. On the rare occasions when you need more power than QuickReport
offers, you should use another printing tool, such as ReportSmith or Crystal Reports.
These third-party products ship with VCL links for both high-quality tools.
NOTE: ReportSmith is still officially
part of the Borland suite of products, but most of its day-to-day operations are
now being handled by a third party. As a result, it does not ship with the new versions
of BCB or Delphi, though it did ship with Delphi 1.0 and Delphi 2.0.
Address2:
Grouping Data in a Report
Reports often have to be presented in groups. For instance, the Address2 program
has a Category field that allows the user to put each record in a separate
category, such as Work, Home, Family, Fun, or
Entertainment. The user wants to be able to generate reports by groups,
as shown in Figure 17.4.
QuickReport makes it easy to create a report of this kind. To get started, drop
down three TQRBand components, setting the BandType property from
the first to rbGroupHeader, the second to rbDetail, and the third
to rbGroupFooter. Also drop down a TQuickReport control and set
its DataSource property to the AddressSource control from the program's
data module.
You now need to drop down a TQRGroup component and set its DataSource
to AddressSource and its DataField to Category. Set the
HeaderBand property to the first TQRBand control and the FooterBand
property to the third TQRBand control.
FIGURE
17.4. A report from the Address2 program
grouped on the Category field.
You can now drop down and hook up some TQRSysData and TQRDBText
controls so that you can display information to the user. The final report should
look like the screen shot shown in Figure 17.5.
FIGURE
17.5. The GroupReport as it appears at
design time.
Troubleshooting Tips
Generating reports with TQuickReport is very simple, but occasionally
things will go wrong. Here are some troubleshooting tips:
- If you see only one record when you are expecting to see many, check to see if
you have set the DataSource field of the TQuickReport control to
the correct data set.
- If one of the fields is blank, check its DataSource and DataField
properties.
Reports in the
Music Program
The Music program appeared in Chapter 16, "Advanced InterBase Concepts."
There are two types of reports in the music program:
- 1. A one-to-many report that shows all the albums associated with each
artist.
2. A group-by report that groups each album under its associated type, such
as jazz or rock.
The next few pages cover both types of reports. Again, my text will be a bit abrupt
at times. The material here is so simple that the best approach is just to give you
a few hints about how to proceed, and then let you hammer out the details by working
live with the tools.
One-to-Many Reports
The one-to-many report from the Music program shows how to use the TQRDetailLink
component. See Figure 17.6.
FIGURE
17.6. The one-to-many Album report as
it appears in print preview mode.
To get started, drop down four TQRBand components. Set the BandType
for the first component to rbPageHeader, the second one to rbGroupHeader,
the third one to rbSubDetail, and the last one to rbPageFooter.
You might find it useful to name each component after its type:
| Component name |
BandType |
| PageHeaderBand |
rbPageHeader |
| GroupHeaderBand |
rbGroupHeader |
| SubDetailBand |
rbSubDetail |
| PageFooterBand |
rbPageFooter |
The header and footer bands should use TQRSysData components to display
standard information such as the report title, page number, and the time and date
the report is printed.
Drop down a TQuickReport component and connect it to the ArtistSource
on the data module. Now drop down a TQRDetailLink component, which is designed
to help you set up one-to-many reports. The TQRDetailLink component should
have its DataSource property set to the ArtistSource from the data
module and its Master property set to QuickReport1. The DetailBand
property should be set to the SubDetailBand and the HeaderBand
property to the GroupHeaderBand.
After you have everything in place, drop down TQRDBText controls to display
the fields of your data. In particular, place one TQRDBText control on the
GroupHeaderBand, set the DataSource property equal to ArtistSource
from the program's data module, and set its DataField property equal to
its FirstLast calculated field.
On the SubDetailBand, drop down four TQRDBText components and
set the DataSource property equal to DMod->AlbumSource. Set
the DataField property for these controls to the Album and Rating
fields, and to the LoudLookup and MediumLookup calculated fields.
The result should look like the image shown in Figure 17.7.
FIGURE
17.7. The one-to-many Album report as
it appears at design time.
You can now set up a menu item on the program's main page that will launch the
form. When you run the program, the form you have created should make a report like
that shown in Figure 17.7.
Using Queries
to Help with Grouped Reports
The second report from the Music program groups all the records in the report
according to their type. For instance, it groups all the jazz albums together and
all the rock albums together.
I've already shown you one grouped report in this chapter. I'm showing you another
because this one uses queries to easily resolve what appears to be a rather complicated
problem. The difficulty here is that you want the report to group according to the
type of album, but to also organize the report so that each album is grouped under
a particular artist. This is a bit tricky to do, given the design of the database.
To remedy the problem, I created a query that generates most of my report for me:
select Types.Types, Artist.First,
Artist.Last, Album.Album, Album.Rating, Loudness.Loudness
from Artist, Album, Types, Loudness
where Artist.Code = Album.GroupCode and
Types.Code=Album.Types and
Loudness.Code = Album.Loudness
group by Types.Types, Artist.Last,
Artist.First, Album.Album, Album.Rating, Loudness.Loudness
This is a fairly straightforward query that gives me all the records in the table
sorted first by record type, and secondly by artist. That way, all the Bob Dylan
albums that I consider to be folk records appear together, and all the Dylan albums
that I think are rock albums are grouped together. Inside of each type, I group the
albums alphabetically. The query also does lookups into the Types and Loudness
tables so I can substitute human-readable strings for the numeric codes found in
the Albums table.
NOTE: Queries not only have the power
to solve complicated problems, but they are also fun to use and can therefore spice
up the mundane task of creating a report. They can also be reused in the main body
of your program. Often, it makes sense to place queries like this inside a stored
procedure.
After creating the query, I need to create a calculated field called FirstLast
that gives me a single string containing the first and last names:
void __fastcall TAlbumGroupForm::AlbumQueryCalcFields(TDataSet *DataSet)
{
AlbumQueryFirstLast->Value =
AlbumQueryFIRST->Value + " " + AlbumQueryLAST->Value;
}
There is no need to create lookup fields for the Loudness and Types
values because I retrieved the strings associated with these fields from the Loudness
and Types tables in the original query.
After writing the query, most of the work I need to do for the report is done.
Now I can simply drop down some TQRBand components, a TQuickReport
component, and a TQRGroup component, and link them together as explained
earlier in the chapter. I perform the grouping on the Types field from the
query.
On the rbGroupHeader band I drop down a TQRDBText control that
is set equal to the Types field. I then add fields rbDetail band
that will display each artist's name, each album's name, and each's loudness and
type. The final report looks like the image shown in Figure 17.8, and the design
time shape of the form is shown in Figure 17.9.
FIGURE
17.8. The Album Group report as it appears
in print preview mode.
FIGURE
17.9. The Album Group report as it appears
at design time.
That is all I want to say about QuickReport in this book. If you want additional
examples of using QuickReport, you should view the kdAdd program.
Printing Forms
One of the easiest ways to print information in BCB is to simply send the image
of a form to a printer. The code for doing this is trivial in the extreme:
void __fastcall TForm1::Print1Click(TObject *Sender)
{
Form1->Print();
}
It doesn't get any easier than this.
On the CD that accompanies this book, you will find a program called PrintForm
that contains a menu item that will print the contents of the current form. This
option will not help you print a dataset, but it will print whatever you can see
on the current form. For instance, the PrintForm program prints the current form
as shown in Figure 17.10, minus the menu, border, and caption areas. The quality
of the picture in the printed output can differ depending on a number of factors.
FIGURE
17.10. The main form of the PrintForm
application just as the user selects the Print option from the menu.
The Address2 program gives you the option of printing a series of forms. In particular,
it iterates through the database, printing one form, on one page, for each record
in the database. This is a waste of resources, but it does produce clean-looking,
easy-to-read reports. However, the user would have to turn the page each time he
or she views a new record. This task is so onerous that it leaves this solution virtually
useless for all but a few, unusual tasks.
Here is the code that prints all the forms in the Address2 program:
void __fastcall TForm1::PrintForms1Click(TObject *Sender)
{
if (ScalingForm->ShowModal() == mrOk)
{
switch (ScalingForm->ScalingOptions->ItemIndex)
{
case 0: PrintScale = poNone; break;
case 1: PrintScale = poProportional; break;
case 2: PrintScale = poPrintToFit; break;
}
Panel2->Visible = False;
DBGrid1->Visible = False;
DMod->AddressTable->First();
while (!DMod->AddressTable->Eof)
{
Print();
DMod->AddressTable->Next();
}
DBGrid1->Visible = True;
Panel2->Visible = True;
}
}
This code first pops up a custom dialog like the one shown in Figure 17.11. This
dialog lets you choose three printing options provided by the TForm object
in a property called PrintScale. The print options (poNone, poProportional,
poPrintToFit) are declared in Forms.hpp.
If you choose PrintToFit, your form will take up the maximum amount of
space it can on the pages you are printing. In short, it will expand to fill the
available space but will continue to remain in proportion. That is, it won't be stretched
but will expand as far as it can in both the horizontal and vertical directions.
When it reaches the limit in either direction, it will stop expanding in both directions,
so that the view you have of the form remains proportional.
The code for this routine also makes invisible all the controls on the form that
are not needed when printing. For instance, the buttons on the form serve no purpose
on the printed form, so I make them invisible. A second solution is to create a custom
form featuring only the portions of this view that need to be printed.
The code then iterates through the database, printing each form:
DMod->AddressTable->First();
while (!DMod->AddressTable->Eof)
{
Print();
DMod->AddressTable->Next();
}
FIGURE
17.11. The Scaling form dialog gives you
a chance to choose how the reports will look when printed.
Be careful, because with this kind of code, it's easy to get stuck in an endless
loop by forgetting to call Next(). This is a no-brainer error that everyone
can see why it is a mistake, but people still tend to make it when they are rushed
or tired.
TPrinter:
Printing Text, Shapes, and Bitmaps
There are two easy ways to send output directly to the printer from a VCL program.
One is to open up the printer as a device and send output to it, and the second is
to use the VCL TPrinter object. The program shown in this section uses the
latter method.
TPrinter provides a TCanvas object initialized to the DC for
the printer. As a result, you can send text or graphics objects to the printer just
as easily as you can send them to the screen.
The PrintGDI program found on the CD that accompanies this book shows how to print
text, shapes, and bitmaps using the TPrinter object. It consists of three
forms, shown in Figures 17.12, 17.13, and 17.14. The source for the program is shown
in Listings 17.1. through 17.6.
FIGURE
17.12. The text from a Shakespearean sonnet
shown in a form. You can use the program to print the text.
Figure
17.13. The form shown here has a TPaintBox
component with some shapes drawn in it. You can print the contents of the paint box.
FIGURE
17.14. This form can display bitmaps and
also send their contents to the printer.
Listing 17.1. The header for
the main module of the PrintGDI program.
///////////////////////////////////////
// File: Main.h
// Project: PrintText
// Copyright (c) 1997 by Charlie Calvert
//
#ifndef MainH
#define MainH
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\Buttons.hpp>
#include <vcl\ExtCtrls.hpp>
#include <vcl\Menus.hpp>
#include <vcl\Dialogs.hpp>
class TForm1 : public TForm
{
__published:
TMainMenu *MainMenu1;
TMenuItem *File1;
TMenuItem *ShapeForm1;
TMenuItem *Print1;
TMenuItem *N1;
TMenuItem *Exit1;
TPrintDialog *PrintDialog1;
TMemo *Memo1;
TMenuItem *Open1;
TOpenDialog *OpenDialog1;
TMenuItem *N2;
TMenuItem *BitmapForm1;
void __fastcall Print1Click(TObject *Sender);
void __fastcall Open1Click(TObject *Sender);
void __fastcall ShapeForm1Click(TObject *Sender);
void __fastcall BitmapForm1Click(TObject *Sender);
private:
void SendToPrinter();
void PrintText(TCanvas *Canvas);
public:
__fastcall TForm1(TComponent* Owner);
};
extern TForm1 *Form1;
#endif
Listing 17.2. The main module
for the PrintGDI program.
///////////////////////////////////////
// File: Main.cpp
// Project: PrintText
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#include <vcl\printers.hpp>
#pragma hdrstop
#include "Main.h"
#include "PaintBoxPrint.h"
#include "PrintBmp1.h"
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void TForm1::PrintText(TCanvas *Canvas)
{
int i, x;
AnsiString S("Test String");
x = Canvas->TextHeight(S);
for (i = 0; i < Memo1->Lines->Count; i++)
{
S = Memo1->Lines->Strings[i];
Canvas->TextOut(1, x * i, S);
}
}
void TForm1::SendToPrinter()
{
TPrinter *APrinter = Printer();
APrinter->BeginDoc();
PrintText(APrinter->Canvas);
APrinter->EndDoc();
}
void __fastcall TForm1::Print1Click(TObject *Sender)
{
if (PrintDialog1->Execute())
{
SendToPrinter();
}
}
void __fastcall TForm1::Open1Click(TObject *Sender)
{
if (OpenDialog1->Execute())
{
Memo1->Lines->LoadFromFile(OpenDialog1->FileName);
void __fastcall TForm1::ShapeForm1Click(TObject *Sender)
{
PaintBoxForm->ShowModal();
}
void __fastcall TForm1::BitmapForm1Click(TObject *Sender)
{
PrintBitmapForm->ShowModal();
}
Listing 17.3. The header for
the PaintBoxPrint module.
///////////////////////////////////////
// File: PaintBoxPrint.h
// Project: PrintText
// Copyright (c) 1997 by Charlie Calvert
//
#ifndef PaintBoxPrintH
#define PaintBoxPrintH
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\ExtCtrls.hpp>
#include <vcl\Buttons.hpp>
#include <vcl\Dialogs.hpp>
class TPaintBoxForm : public TForm
{
__published:
TPaintBox *PaintBox1;
TBitBtn *PrintPictureBtn;
TBitBtn *ShowPictureBtn;
TPrintDialog *PrintDialog1;
void __fastcall PrintPictureBtnClick(TObject *Sender);
private:
void __fastcall ShowData(TCanvas *Canvas);
void SendToPrinter();
public:
__fastcall TPaintBoxForm(TComponent* Owner);
};
extern TPaintBoxForm *PaintBoxForm;
#endif
Listing 17.4. The main module
for the PaintBoxPrint module.
///////////////////////////////////////
// File: PaintBoxPrint.cpp
// Project: PrintText
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#include <vcl\printers.hpp>
#pragma hdrstop
#include "PaintBoxPrint.h"
#pragma resource "*.dfm"
TPaintBoxForm *PaintBoxForm;
__fastcall TPaintBoxForm::TPaintBoxForm(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TPaintBoxForm::ShowData(TCanvas *Canvas)
{
Canvas->Brush->Color = clBlue;
Canvas->Pen->Color = clYellow;
Canvas->Rectangle(0, 0, PaintBox1->Width, PaintBox1->Height);
Canvas->Font->Color = clYellow;
Canvas->TextOut(5, 5, "Hi");
Canvas->Brush->Color = clPurple;
Canvas->Ellipse(25, 25, 150, 150);
}
void TPaintBoxForm::SendToPrinter()
{
if (PrintDialog1->Execute())
{
TPrinter *APrinter = Printer();
APrinter->BeginDoc();
ShowData(APrinter->Canvas);
APrinter->EndDoc();
}
}
void __fastcall TPaintBoxForm::PrintPictureBtnClick(TObject *Sender)
{
switch(dynamic_cast<TButton *>(Sender)->Tag)
{
case 0:
{
SendToPrinter();
break;
}
case 1:
{
ShowData(PaintBox1->Canvas);
break;
}
}
}
Listing 17.5. The header for
the PrintBitmap module.
///////////////////////////////////////
// File: PrintBmp.h
// Project: PrintText
// Copyright (c) 1997 by Charlie Calvert
//
#ifndef PrintBmp1H
#define PrintBmp1H
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\ExtCtrls.hpp>
#include <vcl\Menus.hpp>
#include <vcl\Dialogs.hpp>
class TPrintBitmapForm : public TForm
{
__published:
TImage *Image1;
TMainMenu *MainMenu1;
TMenuItem *File1;
TMenuItem *Open1;
TMenuItem *Print1;
TMenuItem *N1;
TMenuItem *Exit1;
TOpenDialog *OpenDialog1;
TPrintDialog *PrintDialog1;
TMenuItem *Options1;
TMenuItem *Stretch1;
void __fastcall Open1Click(TObject *Sender);
void __fastcall Print1Click(TObject *Sender);
void __fastcall Exit1Click(TObject *Sender);
void __fastcall Stretch1Click(TObject *Sender);
private:
public:
__fastcall TPrintBitmapForm(TComponent* Owner);
};
extern TPrintBitmapForm *PrintBitmapForm;
#endif
Listing 17.6. The main form for
the PrintBitmap module.
///////////////////////////////////////
// File: PrintBmp.cpp
// Project: PrintText
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#include <vcl\printers.hpp>
#pragma hdrstop
#include "PrintBmp1.h"
#pragma resource "*.dfm"
TPrintBitmapForm *PrintBitmapForm;
__fastcall TPrintBitmapForm::TPrintBitmapForm(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TPrintBitmapForm::Open1Click(TObject *Sender)
{
if (OpenDialog1->Execute())
{
Image1->Picture->LoadFromFile(OpenDialog1->FileName);
}
}
void __fastcall TPrintBitmapForm::Print1Click(TObject *Sender)
{
if (PrintDialog1->Execute())
{
TPrinter *APrinter = Printer();
APrinter->BeginDoc();
APrinter->Canvas->Draw(1, 1, Image1->Picture->Bitmap);
APrinter->EndDoc();
}
}
void __fastcall TPrintBitmapForm::Exit1Click(TObject *Sender)
{
Close();
}
void __fastcall TPrintBitmapForm::Stretch1Click(TObject *Sender)
{
Stretch1->Checked = !Stretch1->Checked;
Image1->Stretch = Stretch1->Checked;
}
The main form of the program can print text, such as that shown in Figure 17.12,
where you can see one of Shakespeare's sonnets. To send this text to the printer,
you need to retrieve the printer object from the VCL. This object is declared in
the Printers unit, so you must include that unit in your form. You can the write
the following code:
void TForm1::SendToPrinter()
{
TPrinter *APrinter = Printer();
APrinter->BeginDoc();
PrintText(APrinter->Canvas);
APrinter->EndDoc();
}
This code first calls the Printer method to retrieve an object of type
TPrinter. It then calls the BeginDoc method of the TPrinter
object to start a document. When the printing task is done, you should call EndDoc.
The following method handles the actual printing chores:
void TForm1::PrintText(TCanvas *Canvas)
{
int i, x;
AnsiString S("Test String");
x = Canvas->TextHeight(S);
for (i = 0; i < Memo1->Lines->Count; i++)
{
S = Memo1->Lines->Strings[i];
Canvas->TextOut(1, x * i, S);
}
}
As you can see, this code starts at the beginning of the list of strings and iterates
through them all, using the TPrinter Canvas object to print the
current information. You can change the font, colors, and other aspects of the canvas
in any way you like. Recall that in Chapter 7, "Graphics," I described
how to use the Canvas object.
Printing Shapes to the Printer
One of the great advantages of the code shown in the PrintText method
is that you can use it with any Canvas object. For instance, I could pass
in the Canvas of the main form and then print the output to the main form,
rather than to the printer.
The technique described in the last paragraph is what goes on in the form shown
in Figure 17.13. This form provides the user with two buttons, one for printing information
to a control on the current form and the other for printing information to a printer:
void __fastcall TPaintBoxForm::PrintPictureBtnClick(TObject *Sender)
{
switch(dynamic_cast<TButton *>(Sender)->Tag)
{
case 0:
{
SendToPrinter();
break;
}
case 1:
{
ShowData(PaintBox1->Canvas);
break;
}
}
}
If you opt to send information to the printer, the following code is called:
void TPaintBoxForm::SendToPrinter()
{
if (PrintDialog1->Execute())
{
TPrinter *APrinter = Printer();
APrinter->BeginDoc();
ShowData(APrinter->Canvas);
APrinter->EndDoc();
}
}
This code first pops up a TPrintDialog and lets the user set up the printer,
and switch into color printing mode, if necessary. A document is started, and the
ShowData method is called:
void __fastcall TPaintBoxForm::ShowData(TCanvas *Canvas)
{
Canvas->Brush->Color = clBlue;
Canvas->Pen->Color = clYellow;
Canvas->Rectangle(0, 0, PaintBox1->Width, PaintBox1->Height);
Canvas->Font->Color = clYellow;
Canvas->TextOut(5, 5, "Hi");
Canvas->Brush->Color = clPurple;
Canvas->Ellipse(25, 25, 150, 150);
}
As you can see, this method draws some text and a series of shapes into a canvas.
If the canvas you pass to this program is for the main form or for a control placed
on the main form, the output will appear there. If the canvas you pass in belongs
to the printer, the output will be sent to the printer.
NOTE: One problem that I do not address
in this code involves selecting the proper size and proportions for the current printer.
As a rule, you will find that text or shapes that look okay on the screen will be
far too small when shown on a printer. For hints on how to remedy the situation,
view the code in Forms.pas that shows how to implement the poProportional
and poPrintToFit options discussed earlier in this chapter. This code is
almost all straight Windows API code, and will look virtually identical in C++ and
Object Pascal.
Print Bitmaps
The final portion of the PrintGDI program that might be of interest to some users
involves printing bitmaps. This sounds like it must be a complicated subject, but
the VCL makes it easy.
The key to this process is using a TImage control on the form from which
you want to print a bitmap. The following code can be used to load an image into
that TImage control.
void __fastcall TPrintBitmapForm::Open1Click(TObject *Sender)
{
if (OpenDialog1->Execute())
{
Image1->Picture->LoadFromFile(OpenDialog1->FileName);
}
}
To print the image, just write the following code:
void __fastcall TPrintBitmapForm::Print1Click(TObject *Sender)
{
if (PrintDialog1->Execute())
{
TPrinter *APrinter = Printer();
APrinter->BeginDoc();
APrinter->Canvas->Draw(1, 1, Image1->Picture->Bitmap);
APrinter->EndDoc();
}
}
This code pops up a TPrintDialog, grabs the printer, and begins a document.
You can draw a bitmap on the TPrinter canvas by calling Draw and
passing in the bitmap already loaded into the TImage component. The first
two parameters passed to draw specify where you want the printing to begin as expressed
in X, Y coordinates. The final step is to call EndDoc, which sends a form-feed
to the printer.
That is all I'm going to say about the TPrinter class. There are more
features of this object that you can explore via the online help or by opening up
Printers.hpp. However, the basic facts outlined here should give you most
of the information you need to get started with this object.
Printing
Records in ReportSmith
ReportSmith is no longer an integrated part of the VCL family of products. However,
I will touch on this subject briefly, just to give you a few clues about how to proceed
with the tool if you need it. In particular, I will show how to create a report for
the Address2 program.
NOTE: In the current shipping version
of BCB, there aren't any TReportSmith or TReport components. However,
I am sure that the people now making ReportSmith supply this component with the product.
In the worst-case scenario, you can just use the Windows API command called WinExec
to start ReportSmith and pass as a parameter the name of the report you want to run.
There are also ways to pass macros to ReportSmith, and you can talk to the product
using DDE.
To start creating a report, bring up ReportSmith using the Explorer and select
the type of report you want to make, which is probably a label-based report. Go to
the Tables page in the Report Query dialog, choose Add Table, and select ADDRESS.DB.
ReportSmith understands BDE aliases, so you can use those to help you select a table.
Next, go to the Report Variables page and create a new variable called Filter.
Set its type to String, its title to Filter List, and the prompt
to "What filter do you want to use?". Set the entry to Type-in,
as shown in Figure 17.15. When you are done, choose Add.
FIGURE
17.15. Creating report variables in ReportSmith.
NOTE: You do not have to choose Type-in
as the entry method. In fact, the Address2 program is ideally suited for using the
Choose from a Table method. After you select this method, a work space will appear
in the bottom-right corner of the Report Variables page that enables you to choose
a table and field that contain a list of available entries. In this case, you can
choose the CATS.DB table and the Category field. Now when the user wants
to run the report, he or she will be prompted with a list of valid categories and
can choose the appropriate one, without the likelihood of an error being introduced.
The copy of the ReportSmith report that ships with the CD for this book uses this
method.
Turn to the Selections page, click on the yellow number 1 in the center of the
page, and choose Select SQL selection criteria from the drop-down list. Select the
Category field from the DataFields list box on the left and choose x=y from
the Comparison Operators in the middle list box. Go back to the left-hand list box
and change the combo box at the top so it reads Report Variables rather than Data
Fields. Choose Filter and then set this variable in quotes:
`ADDRESSxDB'.'CATEGORY' = `<<Filter>>'
When you are done, the dialog should look like that in Figure 17.16. Now click
the OK button at the bottom of the dialog.
FIGURE
17.16. Creating SQL selection criteria
in ReportSmith.
The final step in this process is to create derived fields in the Derived Fields
page of the Report Query dialog. The first derived field should combine the FName
and LName fields, so you might want to call this field FirstLast.
After typing in the name, select Add, and the Edit Derived Fields dialog box will
appear. Select FName from the left column:
`ADDRESSxDB'.'FName'
Choose Addition from the middle column:
`ADDRESSxDB'.'FName' +
Add a space by writing the string ` `:
`ADDRESSxDB'.'FName' + ` `
Choose Addition again from the middle column:
`ADDRESSxDB'.'FName' + ` ` +
End by adding the LName field. The string you create should look like
this:
`ADDRESSxDB'.'FName' + ` ` + `ADDRESSxDB'.'LName'
This statement combines the FName and LName fields so that they
produce a single string out of a first and last name:
Kurt Weill
You should then create a second derived field called CityStateZip, which
combines the City, State, and Zip fields:
`ADDRESSxDB'.'CITY' + `, ` + `ADDRESSxDB'.'STATE' + ` ` +
`ADDRESSxDB'.'ZIP'
You have now created the logic behind a report, so choose Done from the bottom
of the Report Query dialog. ReportSmith will then pop up a dialog to fill in the
report variable you created. In other words, it's time for you to fill in the Filter
portion of the following statement:
`ADDRESSxDB'.'CATEGORY' = `<<Filter>>'
You can type the word Family, Work, or whatever value you feel
will return a reasonably sized dataset.
The Insert Field dialog now appears, and you can enter the fields and derived
fields that you have created. The combo box at the top of the dialog enables you
to switch back and forth between data fields and derived fields; you should do so
when you think it's appropriate. For instance, the first field you select will probably
be the derived field called FirstLast, whereas the second will probably
be the data field called Address1. When you are done, the report you create
should look something like the image shown in Figure 17.17.
FIGURE
17.17. A "live data" label report
produced by ReportSmith.
Even the report shown here is probably not totally complete; you might want to
rearrange the location of the fields inside each label and change the size and location
of each individual label. You will find that you can move individual fields around
by simply dragging them with the mouse. The actual decisions you make will be based
on your personal tastes and on the type of labels you have in your office or home.
When you are performing these tasks, you will probably find it easiest to do so by
choosing the File | Page Setup menu option from the ReportSmith main menu.
If you want to change the fonts used in your report, select one of the fields
in a label, right-click on the page, and choose Character from the pop-up menu. Other
options, such as adjusting borders and field height and inserting pictures are available
when you right-click a report.
When you are completely finished preparing a report, choose File | Save to save
the report you have created under the name Address, in the same directory where the
Address2 program resides.
As I stated previously, this brief tutorial on ReportSmith is not meant to be
anything more than a very loosely structured primer. ReportSmith is very easy to
use, and most of the actions you will perform are intuitive variations on the simple
steps already outlined. However, you will probably also want to study the ReportSmith
manuals, online help, and perhaps a third-party book dedicated to this subject.
NOTE: ReportSmith has a fairly sophisticated
query builder in the Selections page of the Report Query dialog. On several occasions,
I have actually abandoned BCB's query builder and opened up ReportSmith to construct
a SQL statement. When I'm done, I just block-copy the SQL code from ReportSmith into
the SQL property of my current Delphi TQuery object. This is a
major kludge, but under some circumstances you might want to consider it as an option.
ReportSmith is a good tool that has the enormous advantage of being easy to use.
In fact, ReportSmith is so easy to use that it turns the task of generating reports
into something very similar to playing. As a result, you can quickly generate elegant-looking
reports and then get back to the more interesting task of writing code.
Summary
In this chapter you have had an overview of printing in BCB. In particular, you
have seen how to use QuickReport, the TPrinter object, and ReportSmith.
You should remember that there might be updates available for QuickReport, and that
another important third-party tool in the printing world is Seagate's Crystal Reports.
The point to grasp when reading this chapter is that BCB makes printing easy.
Whether you are using TPrinter or QuickReport, you can output most information
on a printer with only a few short minutes of work. If the printing task begins to
look complicated, use a SQL query to get the information you need and then print
the output of the query. This is the best course of action in terms of ease of use,
and also in terms of creating an easy to maintain program.
As I said in the beginning, most people regard printing as a fairly boring chore.
There is little in this chapter likely to change anyone's mind on this subject, but
by now you should see that you have the tools necessary to quickly and easily generate
powerful reports that users will love. If you take the time to learn how to use these
tools, you can generate lots of reports easily. This is a skill you simply must have
if you want to work in the client/server database world.
|